

package com.hazelcast.client.impl.connection.tcp;

import com.hazelcast.internal.util.Clock;
import com.hazelcast.logging.ILogger;

import java.util.Random;

public class WaitStrategy {

    private final int initialBackoffMillis;
    private final int maxBackoffMillis;
    private final double multiplier;
    private final double jitter;
    private final Random random = new Random();
    private final ILogger logger;
    private final long clusterConnectTimeoutMillis;
    private final String clusterConnectTimeoutText;
    private int attempt;
    private int currentBackoffMillis;
    private long clusterConnectAttemptBegin;

    WaitStrategy(int initialBackoffMillis, int maxBackoffMillis, double multiplier, long clusterConnectTimeoutMillis, double jitter, ILogger logger) {
        this.initialBackoffMillis = initialBackoffMillis;
        this.maxBackoffMillis = maxBackoffMillis;
        this.multiplier = multiplier;
        this.clusterConnectTimeoutMillis = clusterConnectTimeoutMillis;
        if (clusterConnectTimeoutMillis == Long.MAX_VALUE) {
            // For a better logging output for the default values, we will
            // replace "Long.MAX_VALUE ms" with this in our output.
            clusterConnectTimeoutText = "INFINITE";
        } else {
            clusterConnectTimeoutText = clusterConnectTimeoutMillis + " ms";
        }
        this.jitter = jitter;
        this.logger = logger;
    }

    public void reset() {
        attempt = 0;
        clusterConnectAttemptBegin = Clock.currentTimeMillis();
        currentBackoffMillis = Math.min(maxBackoffMillis, initialBackoffMillis);
    }

    public boolean sleep() {
        attempt++;
        long currentTimeMillis = Clock.currentTimeMillis();
        long timePassed = currentTimeMillis - clusterConnectAttemptBegin;
        if (timePassed > clusterConnectTimeoutMillis) {
            logger.warning(String.format("Unable to get live cluster connection, cluster connect timeout (%s) is " + "reached. Attempt %d.", clusterConnectTimeoutText, attempt));
            return false;
        }

        //random_between
        // Random(-jitter * current_backoff, jitter * current_backoff)
        long actualSleepTime = (long) (currentBackoffMillis + currentBackoffMillis * jitter * (2.0 * random.nextDouble() - 1.0));

        actualSleepTime = Math.min(actualSleepTime, clusterConnectTimeoutMillis - timePassed);

        logger.warning(String.format("Unable to get live cluster connection, retry in %d ms, attempt: %d" + ", cluster connect timeout: %s" + ", max backoff: %d ms", actualSleepTime, attempt, clusterConnectTimeoutText, maxBackoffMillis));

        try {
            Thread.sleep(actualSleepTime);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }

        currentBackoffMillis = (int) Math.min(currentBackoffMillis * multiplier, maxBackoffMillis);
        return true;
    }
}
